package com.gzc.just.play.last.war.centralserver.match;

import com.gzc.just.play.last.war.centralserver.battle.BattleManager;
import com.gzc.just.play.last.war.centralserver.battle.model.BattleRoom;
import com.gzc.just.play.last.war.centralserver.config.CentralServerConfig;
import com.gzc.just.play.last.war.centralserver.service.UserStatusService;
import com.gzc.just.play.last.war.centralserver.match.model.MatchRequest;
import com.gzc.just.play.last.war.centralserver.match.model.MatchResult;
import com.gzc.just.play.last.war.centralserver.match.model.MatchTeam;
import com.gzc.just.play.last.war.centralserver.match.model.MatchType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

@Component("central-battleMatchManager")
public class BattleMatchManager {
    private static final Logger logger = LoggerFactory.getLogger(BattleMatchManager.class);
    private static final long MATCH_REQUEST_TIMEOUT = 300000;
    
    @Autowired
    @org.springframework.context.annotation.Lazy
    private BattleManager battleManager;
    
    @Autowired
    private UserStatusService userService;
    
    @Autowired
    private CentralServerConfig config;
    
    private final Map<Long, MatchRequest> pendingRequests = new ConcurrentHashMap<>();
    
    private final Map<Long, MatchTeam> pendingTeams = new ConcurrentHashMap<>();
    
    private final AtomicLong matchIdGenerator = new AtomicLong(1000);
    
    private final AtomicLong teamIdGenerator = new AtomicLong(1000);
    
    private final AtomicInteger totalMatches = new AtomicInteger(0);
    private final AtomicInteger averageMatchTime = new AtomicInteger(0);
    
    @PostConstruct
    public void init() {
        logger.info("BattleMatchManager initialized");
    }
    
    public CompletableFuture<MatchResult> requestMatch(long userId, MatchType matchType, 
                                                   int mapId, Map<String, Object> preferences) {
        long matchId = matchIdGenerator.incrementAndGet();
        
        logger.info("User {} requesting match, type: {}, mapId: {}, matchId: {}", 
                   userId, matchType, mapId, matchId);
        
        MatchRequest request = new MatchRequest();
        request.setMatchId(matchId);
        request.setUserId(userId);
        request.setMatchType(matchType);
        request.setMapId(mapId);
        request.setPreferences(preferences != null ? preferences : new HashMap<>());
        request.setRequestTime(System.currentTimeMillis());
        request.setStatus(MatchRequest.MatchStatus.PENDING);
        
        pendingRequests.put(matchId, request);
        
        CompletableFuture<MatchResult> matchFuture = new CompletableFuture<>();
        request.setMatchFuture(matchFuture);
        
        tryMatch();
        
        return matchFuture;
    }
    
    public boolean cancelMatch(long userId) {
        MatchRequest request = null;
        for (MatchRequest req : pendingRequests.values()) {
            if (req.getUserId() == userId) {
                request = req;
                break;
            }
        }
        
        if (request != null) {
            pendingRequests.remove(request.getMatchId());
            
            CompletableFuture<MatchResult> future = request.getMatchFuture();
            if (future != null && !future.isDone()) {
                MatchResult result = MatchResult.cancelled(request.getMatchId());
                future.complete(result);
            }
            
            logger.info("User {} cancelled match, matchId: {}", userId, request.getMatchId());
            return true;
        }
        
        MatchTeam team = null;
        for (MatchTeam t : pendingTeams.values()) {
            if (t.hasMember(userId)) {
                team = t;
                break;
            }
        }
        
        if (team != null) {
            return cancelTeamMatch(team.getTeamId());
        }
        
        return false;
    }
    
    public long createTeam(long leaderId, String teamName, MatchType matchType, int maxMembers) {
        long teamId = teamIdGenerator.incrementAndGet();
        
        MatchTeam team = new MatchTeam();
        team.setTeamId(teamId);
        team.setLeaderId(leaderId);
        team.setTeamName(teamName);
        team.setMatchType(matchType);
        team.setMaxMembers(maxMembers);
        team.setCreateTime(System.currentTimeMillis());
        team.addMember(leaderId);
        
        pendingTeams.put(teamId, team);
        
        logger.info("Created team {}, leader: {}, name: {}, type: {}", 
                   teamId, leaderId, teamName, matchType);
        
        return teamId;
    }
    
    public boolean joinTeam(long userId, long teamId) {
        MatchTeam team = pendingTeams.get(teamId);
        if (team == null) {
            logger.warn("Team {} not found for user {} to join", teamId, userId);
            return false;
        }
        
        if (team.isFull()) {
            logger.warn("Team {} is full, user {} cannot join", teamId, userId);
            return false;
        }
        
        if (team.hasMember(userId)) {
            logger.warn("User {} is already in team {}", userId, teamId);
            return false;
        }
        
        team.addMember(userId);
        
        logger.info("User {} joined team {}", userId, teamId);
        
        if (team.isFull()) {
            startTeamMatch(team);
        }
        
        return true;
    }
    
    public boolean leaveTeam(long userId) {
        MatchTeam team = null;
        for (MatchTeam t : pendingTeams.values()) {
            if (t.hasMember(userId)) {
                team = t;
                break;
            }
        }
        
        if (team == null) {
            logger.warn("User {} is not in any team", userId);
            return false;
        }
        
        team.removeMember(userId);
        
        logger.info("User {} left team {}", userId, team.getTeamId());
        
        if (team.getMembers().isEmpty()) {
            pendingTeams.remove(team.getTeamId());
            logger.info("Team {} dissolved (no members)", team.getTeamId());
        }
        else if (team.getLeaderId() == userId) {
            long newLeaderId = team.getMembers().iterator().next();
            team.setLeaderId(newLeaderId);
            logger.info("Team {} leadership transferred from {} to {}", 
                       team.getTeamId(), userId, newLeaderId);
        }
        
        return true;
    }
    
    public boolean cancelTeamMatch(long teamId) {
        MatchTeam team = pendingTeams.get(teamId);
        if (team == null) {
            return false;
        }
        
        pendingTeams.remove(teamId);
        
        for (Long memberId : team.getMembers()) {
            logger.info("Team {} match cancelled for member {}", teamId, memberId);
        }
        
        logger.info("Team {} match cancelled", teamId);
        return true;
    }
    
    private void startTeamMatch(MatchTeam team) {
        long matchId = matchIdGenerator.incrementAndGet();
        
        MatchRequest request = new MatchRequest();
        request.setMatchId(matchId);
        request.setTeamId(team.getTeamId());
        request.setMatchType(team.getMatchType());
        request.setRequestTime(System.currentTimeMillis());
        request.setStatus(MatchRequest.MatchStatus.PENDING);
        
        pendingRequests.put(matchId, request);
        
        CompletableFuture<MatchResult> matchFuture = new CompletableFuture<>();
        request.setMatchFuture(matchFuture);
        
        for (Long memberId : team.getMembers()) {
        }
        
        logger.info("Team {} started matching, matchId: {}", team.getTeamId(), matchId);
        
        tryMatch();
    }
    
    private synchronized void tryMatch() {
        List<MatchRequest> requests = new ArrayList<>(pendingRequests.values());
        
        Map<MatchType, List<MatchRequest>> requestsByType = new HashMap<>();
        for (MatchRequest request : requests) {
            if (request.getStatus() == MatchRequest.MatchStatus.PENDING) {
                requestsByType.computeIfAbsent(request.getMatchType(), k -> new ArrayList<>())
                    .add(request);
            }
        }
        
        for (Map.Entry<MatchType, List<MatchRequest>> entry : requestsByType.entrySet()) {
            MatchType matchType = entry.getKey();
            List<MatchRequest> typeRequests = entry.getValue();
            
            switch (matchType) {
                case SOLO:
                    matchSolo(typeRequests);
                    break;
                case TEAM:
                    matchTeam(typeRequests);
                    break;
                case CUSTOM:
                    matchCustom(typeRequests);
                    break;
            }
        }
    }
    
    private void matchSolo(List<MatchRequest> requests) {
        Map<Integer, List<MatchRequest>> requestsByMap = new HashMap<>();
        
        for (MatchRequest request : requests) {
            if (request.getMapId() > 0) {
                requestsByMap.computeIfAbsent(request.getMapId(), k -> new ArrayList<>()).add(request);
            }
        }
        
        for (Map.Entry<Integer, List<MatchRequest>> entry : requestsByMap.entrySet()) {
            List<MatchRequest> mapRequests = entry.getValue();
            
            while (mapRequests.size() >= 2) {
                List<MatchRequest> match = new ArrayList<>();
                match.add(mapRequests.remove(0));
                match.add(mapRequests.remove(0));
                
                createMatchResult(match);
            }
        }
        
        List<MatchRequest> noMapRequests = new ArrayList<>();
        for (MatchRequest request : requests) {
            if (request.getMapId() <= 0) {
                noMapRequests.add(request);
            }
        }
        
        while (noMapRequests.size() >= 2) {
            List<MatchRequest> match = new ArrayList<>();
            match.add(noMapRequests.remove(0));
            match.add(noMapRequests.remove(0));
            
            createMatchResult(match);
        }
    }
    
    private void matchTeam(List<MatchRequest> requests) {
        List<MatchRequest> teamRequests = new ArrayList<>();
        List<MatchRequest> soloRequests = new ArrayList<>();
        
        for (MatchRequest request : requests) {
            if (request.getTeamId() > 0) {
                teamRequests.add(request);
            } else {
                soloRequests.add(request);
            }
        }
        
        for (int i = 0; i < teamRequests.size(); i++) {
            for (int j = i + 1; j < teamRequests.size(); j++) {
                MatchRequest team1 = teamRequests.get(i);
                MatchRequest team2 = teamRequests.get(j);
                
                if (isTeamMatchCompatible(team1, team2)) {
                    List<MatchRequest> match = new ArrayList<>();
                    match.add(team1);
                    match.add(team2);
                    
                    createMatchResult(match);
                }
            }
        }
        
        for (MatchRequest teamRequest : teamRequests) {
            MatchTeam team = pendingTeams.get(teamRequest.getTeamId());
            if (team != null) {
                int needed = team.getMaxMembers() - team.getMembers().size();
                List<MatchRequest> fillRequests = new ArrayList<>();
                fillRequests.add(teamRequest);
                
                for (MatchRequest soloRequest : soloRequests) {
                    if (needed > 0) {
                        fillRequests.add(soloRequest);
                        needed--;
                        soloRequests.remove(soloRequest);
                    } else {
                        break;
                    }
                }
                
                if (needed == 0) {
                    createMatchResult(fillRequests);
                }
            }
        }
    }
    
    private void matchCustom(List<MatchRequest> requests) {
        Map<String, List<MatchRequest>> preferencesGroups = new HashMap<>();
        
        for (MatchRequest request : requests) {
            String preferenceKey = extractPreferenceKey(request.getPreferences());
            preferencesGroups.computeIfAbsent(preferenceKey, k -> new ArrayList<>()).add(request);
        }
        
        for (List<MatchRequest> groupRequests : preferencesGroups.values()) {
            while (groupRequests.size() >= 2) {
                List<MatchRequest> match = new ArrayList<>();
                match.add(groupRequests.remove(0));
                match.add(groupRequests.remove(0));
                
                createMatchResult(match);
            }
        }
    }
    
    private boolean isTeamMatchCompatible(MatchRequest team1, MatchRequest team2) {
        MatchTeam t1 = pendingTeams.get(team1.getTeamId());
        MatchTeam t2 = pendingTeams.get(team2.getTeamId());
        
        if (t1 == null || t2 == null) {
            return false;
        }
        
        return t1.getMatchType() == t2.getMatchType();
    }
    
    private String extractPreferenceKey(Map<String, Object> preferences) {
        Integer mapId = (Integer) preferences.getOrDefault("mapId", -1);
        return mapId.toString();
    }
    
    private void createMatchResult(List<MatchRequest> matchedRequests) {
        if (matchedRequests.isEmpty()) {
            return;
        }
        
        List<Long> userIds = new ArrayList<>();
        for (MatchRequest request : matchedRequests) {
            if (request.getUserId() > 0) {
                userIds.add(request.getUserId());
            } else if (request.getTeamId() > 0) {
                MatchTeam team = pendingTeams.get(request.getTeamId());
                if (team != null) {
                    userIds.addAll(team.getMembers());
                }
            }
        }
        
        int mapId = selectMapForMatch(matchedRequests);
        String battleName = "Matched Battle " + System.currentTimeMillis();
        BattleRoom battleRoom = (BattleRoom) battleManager.createBattleRoom(userIds, battleName, mapId);
        
        if (battleRoom == null) {
            logger.error("Failed to create battle room for matched users");
            return;
        }
        
        MatchResult matchResult = MatchResult.success(matchedRequests, battleRoom);
        
        for (MatchRequest request : matchedRequests) {
            request.setStatus(MatchRequest.MatchStatus.MATCHED);
            
            CompletableFuture<MatchResult> future = request.getMatchFuture();
            if (future != null && !future.isDone()) {
                future.complete(matchResult);
            }
            
            pendingRequests.remove(request.getMatchId());
        }
        
        for (MatchRequest request : matchedRequests) {
            if (request.getTeamId() > 0) {
                pendingTeams.remove(request.getTeamId());
            }
        }
        
        totalMatches.incrementAndGet();
        long averageTime = calculateAverageMatchTime(matchedRequests);
        averageMatchTime.set((int) averageTime);
        
        logger.info("Created match result for {} requests, battleRoomId: {}, mapId: {}", 
                   matchedRequests.size(), battleRoom.getRoomId(), mapId);
    }
    
    private int selectMapForMatch(List<MatchRequest> matchedRequests) {
        for (MatchRequest request : matchedRequests) {
            if (request.getMapId() > 0) {
                return request.getMapId();
            }
        }
        
        List<Integer> availableMaps = config.getAvailableMaps();
        if (!availableMaps.isEmpty()) {
            Random random = new Random();
            return availableMaps.get(random.nextInt(availableMaps.size()));
        }
        
        return 1;
    }
    
    private long calculateAverageMatchTime(List<MatchRequest> matchedRequests) {
        long currentTime = System.currentTimeMillis();
        long totalTime = 0;
        
        for (MatchRequest request : matchedRequests) {
            totalTime += currentTime - request.getRequestTime();
        }
        
        return totalTime / matchedRequests.size();
    }
    
    public void cleanupExpiredRequests() {
        long currentTime = System.currentTimeMillis();
        List<Long> expiredRequests = new ArrayList<>();
        
        for (MatchRequest request : pendingRequests.values()) {
            if (currentTime - request.getRequestTime() > MATCH_REQUEST_TIMEOUT) {
                expiredRequests.add(request.getMatchId());
            }
        }
        
        for (Long matchId : expiredRequests) {
            MatchRequest request = pendingRequests.remove(matchId);
            if (request != null) {
                CompletableFuture<MatchResult> future = request.getMatchFuture();
                if (future != null && !future.isDone()) {
                    MatchResult result = MatchResult.timeout(matchId);
                    future.complete(result);
                }
                
                logger.info("Match request {} timed out", matchId);
            }
        }
    }
    
    public Map<String, Object> getMatchStatistics() {
        Map<String, Object> stats = new HashMap<>();
        stats.put("totalMatches", totalMatches.get());
        stats.put("averageMatchTime", averageMatchTime.get());
        stats.put("pendingRequests", pendingRequests.size());
        stats.put("pendingTeams", pendingTeams.size());
        return stats;
    }
}
